#pragma once

#include <iostream>
#include <string>
#include <queue>
#include <unistd.h>
#include <pthread.h>

namespace  Mango
{
    const int g_num = 5;
    template<class T>
    class ThreadPool
    {
        private:
            //构造函数+拷贝构造+赋值重载都禁用掉
            //构造函数必须得实现，因为没有构造函数就没办法初始化
            //但是必须私有化(单例模式) ->这个类不能用来定义对象
            ThreadPool(int num = g_num)
                :_num(num)
            {
                //初始化互斥锁和条件变量,属性设置为nullptr
                pthread_mutex_init(&_mtx,nullptr);
                pthread_cond_init(&_cond,nullptr);
            }
            ThreadPool(const ThreadPool<T>& tp) = delete;
            ThreadPool<T>& operator=(ThreadPool<T> &tp) = delete;
        public:
            ~ThreadPool()
            {
                //释放锁和条件变量
                pthread_mutex_destroy(&_mtx);
                pthread_cond_destroy(&_cond);
            }


            void Lock() //加锁
            {
                pthread_mutex_lock(&_mtx);
            }
            void UnLock()   //解锁
            {
                pthread_mutex_unlock(&_mtx);
            }
            void Wait() //在条件变量下等待
            {
                pthread_cond_wait(&_cond,&_mtx);
            }
            void WaitUp()//唤醒在条件变量下等待的进程
            {
                pthread_cond_signal(&_cond);
            }
            bool IsEmpty()//判断任务队列中是否有任务(是否为空)
            {
                return task_queue.empty();
            }

            static ThreadPool<T> *GetInstance()//获得这个唯一的对象
            {
                //单例本身会在任何场景，任何环境下被调用
                //GetInstance()：被多线程重入，进而导致线程安全的问题
                //我们只需要抱着检测当前创建对象和检测的过程是原子的就行
                //如何解决呢?->加一把锁
                static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//因为是static,所以不用初始化也不用释放

                if(ins == nullptr)//当前单例对象还没有被创建
                {
                    pthread_mutex_lock(&lock);
                    //双判定，减少锁的争用，提高获取单例的效率！
                    if(ins == nullptr)
                    {
                        ins = new ThreadPool<T>();
                        ins->InitThreadPool();//初始化线程池
                        std::cout << "首次加载对象" << std::endl;
                    }
                    pthread_mutex_unlock(&lock);
                }
                //单例对象已经被创建过了,直接返回
                return ins;
            }

            void PushTask(const T& in) 
            {
                //访问临界资源前先加锁
                Lock();
                task_queue.push(in);
                UnLock();
                //唤醒在条件变量下等待的线程执行任务
                WaitUp();
            }

            //拿任务的时候,因为是在加锁的上下文中拿任务,所以可以直接拿
            void PopTask(T* out)
            {
                *out = task_queue.front();
                task_queue.pop();
            }

            //在类中要让线程执行类内成员方法,是不可行的！因为类内的成员函数第一个参数是默认的this指针！！！
            //因为现在是静态方法,不能访问类内的属性
            //解决办法:pthread_create的第四个参数传递this指针   
            static void *Rountine(void *args)
            {
                pthread_detach(pthread_self());//线程分离,后序就不需要等待该进程了
                ThreadPool<T>* tp = (ThreadPool<T>*)args;

                //竞争任务
                while(1)
                {
                    tp->Lock();//任务队列是临界资源,要访问临界资源,先加锁保护(先把任务队列锁住)

                    //检测任务队列当中有没有任务,因为可能存在伪唤醒,所以用while不能用if
                    while(tp->IsEmpty())
                    {
                        //任务队列为空，线程该做什么呢？？-> 将线程挂起,等待有任务的时候被唤醒
                        tp->Wait();
                    }

                    //来到这里,说明有任务了
                    T t;
                    tp->PopTask(&t);//输出型参数

                    //先解锁在运行任务
                    //把任务从任务队列当中取出了,这个任务也就从任务队列当中移除
                    //这个任务属于当前线程,不再属于临界资源
                    //所以处理任务应该在解锁之后处理,当我们把锁释放掉之后,当前线程在处理这个任务,
                    //其它线程可能征征用锁,然后判断+处理,所以可能存在有多个线程同时跑任务
                    tp->UnLock();
                    t();//t.operator() -> 相当于t.Run()
                }
            }

            void InitThreadPool()//初始化线程池创建线程
            {
                pthread_t tid;
                for(int i = 0;i<_num;i++)
                {
                    //第四个参数传this指针,方便我们静态成员函数Rountine访问类内的成员
                    pthread_create(&tid,nullptr,Rountine,(void*)this);
                }
            }

        private:
            int _num;//表示线程池中有多少个线程

             // 外部存在一个或者多个线程向任务队列当中塞入任务
            // 线程池内部有多个线程竞争式的从任务队列当中拿任务
            // ->典型的生产者消费者模型 ,这个任务队列就是临界资源
            std::queue<T> task_queue;//任务队列->是临界资源

            pthread_mutex_t _mtx;//互斥锁,保护临界资源

            pthread_cond_t  _cond;//条件变量,当没有任务的时候,线程要挂起等待

            static ThreadPool<T> *ins;//指向这个类唯一的对象
    };
    //在类外初始化
    template<class T>
    //类型  作用域::名字
    ThreadPool<T>* ThreadPool<T>::ins = nullptr;
}
